#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File    :   networkEvolutionRulesAnalysis.py
@Contact :   zhujuxing@foxmail.com
@License :   (C)Copyright 2021-2022

@Modify Time      @Author    @Version    @Description
------------      -------    --------    -----------
2021/6/16 12:53   zhujuxing      1.0         None
"""

import math
import random
import re

import networkx as nx
import pandas as pd


def generate_network_evolution_condition(gpath, t):
    t, cdf = read_data(gpath, t)
    cdf1 = generate_failure_state_multi_component_single_failuremode(cdf, t)
    cdf1 = generate_failure_state_multi_component_multi_failuremode(cdf1)
    time_set = generate_time_point_set_of_evolution_condition(cdf1)
    evol = generate_evolution_condition_raw(cdf1, t, time_set)
    evol = format_evol(evol)
    return evol


def time_format(x: str) -> float:
    """
    转换时间单位的函数，将所有时间单位统一为小时
    """
    result = 0.0
    if isinstance(x, str):
        s = re.findall(r"\d+", x)[0]
        time = float(s)
        if ('年' in x) or ('y' in x):
            result = 365 * 24 * time
        elif ('m' in x) or ('分' in x):
            result = time / 60
        elif ('s' in x) or ('秒' in x):
            result = time / 3600
        elif ('h' in x) or ('小时' in x):
            result = time
    return result


def is_hardware(x: str) -> bool:
    if x in ('DCGW', 'Board', 'Server', 'EOR', 'TOR', 'Edge', 'PL'):
        return True
    else:
        return False


def sample_time_form_distribution(mean_val: float, distribution: str, seed=None) -> float:
    if not seed:
        delta = random.random()
    elif type(seed) == float:
        if (seed < 1) and (seed > 0):
            delta = seed
        else:
            raise Exception("给定种子应在[0,1]之间")
    else:
        raise Exception("给定种子格式不正确")

    sample_time = 0.0
    if distribution == "常数":
        sample_time = mean_val
    elif distribution == "均匀型":
        sample_time = 2 * mean_val * delta
    elif distribution == "正态型":
        # sample_time =
        pass
    elif distribution == "指数型":
        sample_time = - math.log(delta) * mean_val
    elif distribution == "指数故障间隔时间":
        pass
    else:
        raise Exception("未定义此类分布。")
    return sample_time


def read_data(data, t: int) -> (int, pd.DataFrame):
    time2hour_t = t * 365 * 24
    if type(data) == str:
        if data.endswith(".gpickle"):
            g = nx.read_gpickle()
        else:
            raise Exception("在进行演化条件生成时，读取的外部文件格式错误。")
    else:
        g = data

    ndf = g.node_info
    edf = g.edge_info
    fdf = g.fail_info

    ndf = ndf.merge(fdf, on=['Type'])
    edf = edf.merge(fdf, on=['Type'])
    ndf["FailureTime"] = 0
    ndf["RepairTime"] = 0
    edf["FailureTime"] = 0
    edf["RepairTime"] = 0
    ndf = ndf.rename(columns={"NodeID": "ID"})
    edf = edf.rename(columns={"EdgeID": "ID"})
    ndf = ndf[
        ['ID', 'Type', 'FailType', 'MTBF', 'FailDistri', 'FDR', 'FDT', 'FARR', 'FART', 'MTTR',
         'RecoDistri']].copy(deep=True)
    edf = edf[
        ['ID', 'Type', 'FailType', 'MTBF', 'FailDistri', 'FDR', 'FDT', 'FARR', 'FART', 'MTTR',
         'RecoDistri']].copy(deep=True)
    cdf = ndf.append(edf)
    return time2hour_t, cdf


def generate_failure_state_single_component_single_failuremode(x: pd.DataFrame, t_period):
    """
    单个构件、单个模式生成状态
    Parameters
    ----------
    x : df行值
    t_period : 周期

    Returns
    -------
    fail_time : 单个构件单个模式失效列表
    reco_time : 单个构件单个模式恢复列表

    """

    fail_time = []
    reco_time = []
    t = 0
    mtbf = time_format(x['MTBF'])
    # FDR = x['FDR']
    fdt = time_format(x['FDT'])
    farr = x['FARR']
    fart = time_format(x['FART'])
    mttr = time_format(x['MTTR'])
    fail_distribution = x['FailDistri']
    recover_distribution = x['RecoDistri']
    component_type = x['Type']
    # t_chk = time_format(x['FDPT'])
    while True:
        if t > t_period:
            break
        else:
            t_f = sample_time_form_distribution(mtbf, fail_distribution)
            t_hr = sample_time_form_distribution(mttr, recover_distribution)
            t_r = cal_repair_time(farr, fart, fdt, component_type, t_hr)

            if (t + t_f) <= t_period:
                fail_time.append(t + t_f)
            else:
                break
            if (t + t_f + t_r) <= t_period:
                reco_time.append(t + t_f + t_r)
            else:
                break
            t = t + t_f + t_r
    return fail_time, reco_time


def cal_repair_time(FARR, FART, FDT, component_type, t_hr):
    t_r = FDT
    if is_hardware(component_type):
        t_r += t_hr
    elif random.random() < FARR:
        t_r += FART
    else:
        t_r += t_hr
    return t_r


def generate_failure_state_multi_component_single_failuremode(cdf: pd.DataFrame,
                                                              t_period: float) -> pd.DataFrame:
    cdf_temp = cdf.apply(generate_failure_state_single_component_single_failuremode, args=(t_period,), axis=1)
    # cdf_temp = cdf.apply(lambda x: generate_failure_state_single_component_single_failuremode(x, t_period), axis=1)
    cdf['FailureTime'] = cdf_temp.apply(lambda x: x[0])
    cdf['RepairTime'] = cdf_temp.apply(lambda x: x[1])
    cdf1 = cdf[['ID', 'FailureTime', 'RepairTime']].copy(deep=True)
    return cdf1


def generate_failure_state_single_component_multi_failuremode(x):
    fail_time = []
    reco_time = []
    for i in x['FailureTime']:
        fail_time.extend(i)
    fail_time.sort()
    for i in x['RepairTime']:
        reco_time.extend(i)
    reco_time.sort()
    df = x.iloc[0]
    df['FailureTime'] = fail_time
    df['RepairTime'] = reco_time
    return df


def generate_failure_state_multi_component_multi_failuremode(cdf1):
    temp = cdf1.groupby('ID').apply(generate_failure_state_single_component_multi_failuremode)
    temp.index = list(range(len(temp)))
    return temp


def generate_time_point_set_of_evolution_condition(cdf1):
    time_set = set([])
    for i in cdf1['FailureTime']:
        time_set = time_set | set(i)
    for i in cdf1['RepairTime']:
        time_set = time_set | set(i)
    time_set = [i for i in time_set]
    time_set.sort()
    print('演化态共有%d个' % len(time_set))
    return time_set


def generate_evolution_condition_raw(cdf1, T, time_set):
    t = 0
    ftl = cdf1['FailureTime'].to_list()
    rtl = cdf1['RepairTime'].to_list()
    all_fail_component_set = []
    all_recovcer_component_set = []
    while t <= T:
        fail_time_list = [i[0] if i != [] else T for i in ftl]
        recover_time_list = [i[0] if i != [] else T for i in rtl]
        fail_time = min(fail_time_list)
        recover_time = min(recover_time_list)
        fail_component_index = fail_time_list.index(fail_time)
        recover_component_index = recover_time_list.index(recover_time)
        fail_set = []
        recover_set = []
        if fail_time < recover_time:
            fail_set.append(cdf1.loc[fail_component_index, 'ID'])
            t = fail_time
            ftl[fail_component_index] = ftl[fail_component_index][1:]
        elif fail_time > recover_time:
            recover_set.append(cdf1.loc[recover_component_index, "ID"])
            t = recover_time
            rtl[recover_component_index] = rtl[recover_component_index][1:]
        else:
            break
        all_fail_component_set.append(fail_set)
        all_recovcer_component_set.append(recover_set)
    evol = pd.DataFrame([all_fail_component_set, all_recovcer_component_set])
    evol = evol.T
    evol_time = [time_set[i + 1] - time_set[i] for i in range(len(time_set) - 1)]
    evol_time.append(T - time_set[-1])
    evol.columns = ['EvolFailComponentsSet', 'EvolRecoComponentsSet']
    try:
        evol.insert(0, 'EvolTime', evol_time)
    except Exception as e:
        print("演化时间过长，需要截取%s" % str(e))
        evol.insert(0, 'EvolTime', evol_time[:len(evol)])
    return evol


def format_evol(evol):
    t = 0

    def time_add(x):
        nonlocal t
        result = [t, t + x]
        t += x
        return result

    evol['EvolTime'] = evol['EvolTime'].apply(time_add)

    # fail_components_set = []

    # def to_fail_components_set(x):
    #     nonlocal fail_components_set
    #     if len(x['EvolRecoComponentsSet']) == 0:
    #         fail_components_set.append(x['EvolFailComponentsSet'][0])
    #     else:
    #         fail_components_set.remove(x['EvolRecoComponentsSet'][0])
    #     return fail_components_set.copy()

    # evol['EvolFailComponentsSet'] = evol.apply(to_fail_components_set, axis=1)
    return evol
